summaryrefslogtreecommitdiff
path: root/app/[lng]/procurement/(procurement)
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]/procurement/(procurement)')
-rw-r--r--app/[lng]/procurement/(procurement)/bid-projects/page.tsx74
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary-tech-sales-hull/page.tsx61
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary-tech-sales-ship/page.tsx61
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary-tech-sales-top/page.tsx61
-rw-r--r--app/[lng]/procurement/(procurement)/cbe-tech/page.tsx67
-rw-r--r--app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx22
-rw-r--r--app/[lng]/procurement/(procurement)/evaluation-input/page.tsx135
-rw-r--r--app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx11
-rw-r--r--app/[lng]/procurement/(procurement)/form-list/page.tsx75
-rw-r--r--app/[lng]/procurement/(procurement)/report/page.tsx69
-rw-r--r--app/[lng]/procurement/(procurement)/rfq-tech/[id]/cbe/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/rfq-tech/[id]/layout.tsx89
-rw-r--r--app/[lng]/procurement/(procurement)/rfq-tech/[id]/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/rfq-tech/[id]/tbe/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/rfq-tech/page.tsx76
-rw-r--r--app/[lng]/procurement/(procurement)/tag-numbering/page.tsx74
-rw-r--r--app/[lng]/procurement/(procurement)/tasks/page.tsx63
-rw-r--r--app/[lng]/procurement/(procurement)/tbe-tech/page.tsx67
-rw-r--r--app/[lng]/procurement/(procurement)/tech-project-avl/page.tsx85
-rw-r--r--app/[lng]/procurement/(procurement)/tech-vendor-candidates/page.tsx78
-rw-r--r--app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/items/page.tsx48
-rw-r--r--app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/layout.tsx82
-rw-r--r--app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/rfq-history/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/tech-vendors/page.tsx58
25 files changed, 184 insertions, 1447 deletions
diff --git a/app/[lng]/procurement/(procurement)/bid-projects/page.tsx b/app/[lng]/procurement/(procurement)/bid-projects/page.tsx
deleted file mode 100644
index 2039e5b2..00000000
--- a/app/[lng]/procurement/(procurement)/bid-projects/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getBidProjectLists } from "@/lib/bidding-projects/service"
-import { searchParamsBidProjectsCache } from "@/lib/bidding-projects/validation"
-import { BidProjectsTable } from "@/lib/bidding-projects/table/projects-table"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsBidProjectsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getBidProjectLists({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 견적 프로젝트 리스트
- </h2>
- <p className="text-muted-foreground">
- SAP(S-ERP)로부터 수신한 견적 프로젝트 데이터입니다. 기술영업의 Budgetary RFQ에서 사용됩니다.
- {/* <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <BidProjectsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-hull/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-tech-sales-hull/page.tsx
deleted file mode 100644
index b1be29db..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-hull/page.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { searchParamsHullCache } from "@/lib/techsales-rfq/validations"
-import { getTechSalesHullRfqsWithJoin } from "@/lib/techsales-rfq/service"
-import { getValidFilters } from "@/lib/data-table"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { RFQListTable } from "@/lib/techsales-rfq/table/rfq-table"
-import { type SearchParams } from "@/types/table"
-import * as React from "react"
-
-interface HullRfqPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function HullRfqPage(props: HullRfqPageProps) {
- // searchParams를 await하여 resolve
- const searchParams = await props.searchParams
-
- // 해양 HULL용 파라미터 파싱
- const search = searchParamsHullCache.parse(searchParams);
- const validFilters = getValidFilters(search.filters);
-
- // 기술영업 해양 Hull RFQ 데이터를 Promise.all로 감싸서 전달
- const promises = Promise.all([
- getTechSalesHullRfqsWithJoin({
- ...search, // 모든 파라미터 전달 (page, perPage, sort, basicFilters, filters 등)
- filters: validFilters, // 고급 필터를 명시적으로 오버라이드 (파싱된 버전)
- })
- ])
-
- return (
- <Shell variant="fullscreen" className="h-full"> {/* fullscreen variant 사용 */}
- {/* 고정 헤더 영역 */}
- <div className="flex-shrink-0">
- <div className="flex items-center justify-between">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 기술영업-해양 Hull RFQ
- </h2>
- </div>
- </div>
- </div>
-
- {/* 테이블 영역 - 남은 공간 모두 차지 */}
- <div className="flex-1 min-h-0">
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={8}
- searchableColumnCount={2}
- filterableColumnCount={3}
- cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RFQListTable promises={promises} className="h-full" rfqType="HULL" />
- </React.Suspense>
- </div>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-ship/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-tech-sales-ship/page.tsx
deleted file mode 100644
index b7bf9d15..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-ship/page.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { searchParamsShipCache } from "@/lib/techsales-rfq/validations"
-import { getTechSalesShipRfqsWithJoin } from "@/lib/techsales-rfq/service"
-import { getValidFilters } from "@/lib/data-table"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { RFQListTable } from "@/lib/techsales-rfq/table/rfq-table"
-import { type SearchParams } from "@/types/table"
-import * as React from "react"
-
-interface RfqPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqPage(props: RfqPageProps) {
- // searchParams를 await하여 resolve
- const searchParams = await props.searchParams
-
- // 조선용 파라미터 파싱
- const search = searchParamsShipCache.parse(searchParams);
- const validFilters = getValidFilters(search.filters);
-
- // 기술영업 조선 RFQ 데이터를 Promise.all로 감싸서 전달
- const promises = Promise.all([
- getTechSalesShipRfqsWithJoin({
- ...search, // 모든 파라미터 전달 (page, perPage, sort, basicFilters, filters 등)
- filters: validFilters, // 고급 필터를 명시적으로 오버라이드 (파싱된 버전)
- })
- ])
-
- return (
- <Shell variant="fullscreen" className="h-full"> {/* fullscreen variant 사용 */}
- {/* 고정 헤더 영역 */}
- <div className="flex-shrink-0">
- <div className="flex items-center justify-between">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 기술영업-조선 RFQ
- </h2>
- </div>
- </div>
- </div>
-
- {/* 테이블 영역 - 남은 공간 모두 차지 */}
- <div className="flex-1 min-h-0">
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={8}
- searchableColumnCount={2}
- filterableColumnCount={3}
- cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RFQListTable promises={promises} className="h-full" rfqType="SHIP" />
- </React.Suspense>
- </div>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-top/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-tech-sales-top/page.tsx
deleted file mode 100644
index f84a9794..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-top/page.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { searchParamsTopCache } from "@/lib/techsales-rfq/validations"
-import { getTechSalesTopRfqsWithJoin } from "@/lib/techsales-rfq/service"
-import { getValidFilters } from "@/lib/data-table"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { RFQListTable } from "@/lib/techsales-rfq/table/rfq-table"
-import { type SearchParams } from "@/types/table"
-import * as React from "react"
-
-interface HullRfqPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function HullRfqPage(props: HullRfqPageProps) {
- // searchParams를 await하여 resolve
- const searchParams = await props.searchParams
-
- // 해양 TOP용 파라미터 파싱
- const search = searchParamsTopCache.parse(searchParams);
- const validFilters = getValidFilters(search.filters);
-
- // 기술영업 해양 TOP RFQ 데이터를 Promise.all로 감싸서 전달
- const promises = Promise.all([
- getTechSalesTopRfqsWithJoin({
- ...search, // 모든 파라미터 전달 (page, perPage, sort, basicFilters, filters 등)
- filters: validFilters, // 고급 필터를 명시적으로 오버라이드 (파싱된 버전)
- })
- ])
-
- return (
- <Shell variant="fullscreen" className="h-full"> {/* fullscreen variant 사용 */}
- {/* 고정 헤더 영역 */}
- <div className="flex-shrink-0">
- <div className="flex items-center justify-between">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 기술영업-해양 TOP RFQ
- </h2>
- </div>
- </div>
- </div>
-
- {/* 테이블 영역 - 남은 공간 모두 차지 */}
- <div className="flex-1 min-h-0">
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={8}
- searchableColumnCount={2}
- filterableColumnCount={3}
- cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RFQListTable promises={promises} className="h-full" rfqType="TOP" />
- </React.Suspense>
- </div>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/cbe-tech/page.tsx b/app/[lng]/procurement/(procurement)/cbe-tech/page.tsx
deleted file mode 100644
index 4dadc58f..00000000
--- a/app/[lng]/procurement/(procurement)/cbe-tech/page.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getAllCBE } from "@/lib/rfqs-tech/service"
-import { searchParamsCBECache } from "@/lib/rfqs-tech/validations"
-import { AllCbeTable } from "@/lib/cbe-tech/table/cbe-table"
-import * as React from "react"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-
-interface IndexPageProps {
- params: {
- lng: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqCBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
-
- // URL 쿼리 파라미터에서 타입 추출
- const searchParams = await props.searchParams
-
- // SearchParams 파싱 (Zod)
- const search = searchParamsCBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- // 현재 선택된 타입의 데이터 로드
- const promises = Promise.all([
- getAllCBE({
- ...search,
- filters: validFilters,
- })
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- Commercial Bid Evaluation
- </h2>
- <p className="text-muted-foreground">
- 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br/>
- 체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <AllCbeTable promises={promises}/>
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx
new file mode 100644
index 00000000..3a403620
--- /dev/null
+++ b/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx
@@ -0,0 +1,22 @@
+import { EvaluationPage } from "@/lib/evaluation-submit/evaluation-page"
+import { Metadata } from "next"
+
+export const metadata: Metadata = {
+ title: "평가 작성",
+ description: "협력업체 평가를 작성합니다",
+}
+
+interface PageProps {
+ params: {
+ id: string
+ }
+}
+
+export default function Page({ params }: PageProps) {
+ return <EvaluationPage />
+}
+
+export async function generateStaticParams() {
+ // 동적 경로이므로 빈 배열 반환
+ return []
+} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx
new file mode 100644
index 00000000..2cf5449f
--- /dev/null
+++ b/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx
@@ -0,0 +1,135 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { getServerSession } from "next-auth"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { LogIn } from "lucide-react"
+import { getSHIEvaluationSubmissions } from "@/lib/evaluation-submit/service"
+import { getSHIEvaluationsSubmitSchema } from "@/lib/evaluation-submit/validation"
+import { SHIEvaluationSubmissionsTable } from "@/lib/evaluation-submit/table/submit-table"
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = getSHIEvaluationsSubmitSchema.parse(searchParams)
+ const validFilters = getValidFilters(search.filters)
+
+ // Get session
+ const session = await getServerSession(authOptions)
+
+ // Check if user is logged in
+ if (!session || !session.user) {
+ // Return login required UI instead of redirecting
+ return (
+ <Shell className="gap-6">
+ <div className="flex items-center justify-between">
+ <div>
+ <div className="flex items-center gap-2">
+ <h2 className="text-2xl font-bold tracking-tight">
+ 정기평가
+ </h2>
+ </div>
+ <p className="text-muted-foreground">
+ 요청된 정기평가를 입력하고 제출할 수 있습니다.
+ </p>
+ </div>
+ </div>
+
+ <div className="flex flex-col items-center justify-center py-12 text-center">
+ <div className="rounded-lg border border-dashed p-10 shadow-sm">
+ <h3 className="mb-2 text-xl font-semibold">로그인이 필요합니다</h3>
+ <p className="mb-6 text-muted-foreground">
+ 정기평가를 확인하려면 먼저 로그인하세요.
+ </p>
+ <Button size="lg" asChild>
+ <Link href="/partners">
+ <LogIn className="mr-2 h-4 w-4" />
+ 로그인하기
+ </Link>
+ </Button>
+ </div>
+ </div>
+ </Shell>
+ )
+ }
+
+ const userId = session.user.id
+
+ // Validate vendorId (should be a number)
+ const idAsNumber = Number(userId)
+
+
+ if (isNaN(idAsNumber)) {
+ // Handle invalid vendor ID (this shouldn't happen if authentication is working properly)
+ return (
+ <Shell className="gap-6">
+ <div className="flex items-center justify-between">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 정기평가
+ </h2>
+ </div>
+ </div>
+ <div className="flex flex-col items-center justify-center py-12 text-center">
+ <div className="rounded-lg border border-dashed p-10 shadow-sm">
+ <h3 className="mb-2 text-xl font-semibold">계정 오류</h3>
+ <p className="mb-6 text-muted-foreground">
+ 관리자에게 문의하세요.
+ </p>
+ </div>
+ </div>
+ </Shell>
+ )
+ }
+
+ // If we got here, we have a valid vendor ID
+ const promises = Promise.all([
+ getSHIEvaluationSubmissions({
+ ...search,
+ filters: validFilters,
+ }, idAsNumber)
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 정기평가
+ </h2>
+ <p className="text-muted-foreground">
+ 요청된 정기평가를 입력하고 제출할 수 있습니다.
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* DateRangePicker can go here */}
+ </React.Suspense>
+
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <SHIEvaluationSubmissionsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx
index 088ae75b..9ec30b66 100644
--- a/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx
+++ b/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx
@@ -16,7 +16,7 @@ import { Badge } from "@/components/ui/badge"
import { getDefaultEvaluationYear, searchParamsEvaluationTargetsCache } from "@/lib/evaluation-target-list/validation"
import { getEvaluationTargets } from "@/lib/evaluation-target-list/service"
import { EvaluationTargetsTable } from "@/lib/evaluation-target-list/table/evaluation-target-table"
-
+import { InformationButton } from "@/components/information/information-button"
export const metadata: Metadata = {
title: "협력업체 평가 대상 확정",
description: "협력업체 평가 대상을 확정하고 담당자를 지정합니다.",
@@ -66,9 +66,12 @@ export default async function EvaluationTargetsPage(props: EvaluationTargetsPage
<div className="flex items-center justify-between space-y-2">
<div className="flex items-center justify-between space-y-2">
<div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">
- 협력업체 평가 대상 확정
- </h2>
+ <div className="flex items-center gap-2">
+ <h2 className="text-2xl font-bold tracking-tight">
+ 협력업체 평가 대상 확정
+ </h2>
+ <InformationButton pagePath="evcp/evaluation-target-list" />
+ </div>
<Badge variant="outline" className="text-sm">
{currentEvaluationYear}년도
</Badge>
diff --git a/app/[lng]/procurement/(procurement)/form-list/page.tsx b/app/[lng]/procurement/(procurement)/form-list/page.tsx
deleted file mode 100644
index a6cf7d9e..00000000
--- a/app/[lng]/procurement/(procurement)/form-list/page.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { searchParamsCache } from "@/lib/form-list/validation"
-import { ItemsTable } from "@/lib/items/table/items-table"
-import { getFormLists } from "@/lib/form-list/service"
-import { FormListsTable } from "@/lib/form-list/table/formLists-table"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getFormLists({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 레지스터 목록 from S-EDP
- </h2>
- <p className="text-muted-foreground">
- 협력업체 데이터 입력을 위한 레지스터 목록 리스트입니다.{" "}
- {/* <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <FormListsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/report/page.tsx b/app/[lng]/procurement/(procurement)/report/page.tsx
index 800fbd8b..adeb31aa 100644
--- a/app/[lng]/procurement/(procurement)/report/page.tsx
+++ b/app/[lng]/procurement/(procurement)/report/page.tsx
@@ -1,5 +1,3 @@
-
-// app/procurement/dashboard/page.tsx
import * as React from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { Shell } from "@/components/shell";
@@ -7,29 +5,34 @@ import { ErrorBoundary } from "@/components/error-boundary";
import { getDashboardData } from "@/lib/dashboard/service";
import { DashboardClient } from "@/lib/dashboard/dashboard-client";
-// 대시보드 데이터 로딩 컴포넌트
-async function DashboardContent() {
+export default async function IndexPage() {
+ // domain을 명시적으로 전달
+ const domain = "procurement";
+
try {
- const data = await getDashboardData("procurement");
+ // 서버에서 직접 데이터 fetch
+ const dashboardData = await getDashboardData(domain);
- const handleRefresh = async () => {
- "use server";
- return await getDashboardData("procurement");
- };
-
return (
- <DashboardClient
- initialData={data}
- onRefresh={handleRefresh}
- />
+ <Shell className="gap-2">
+ <DashboardClient initialData={dashboardData} />
+ </Shell>
);
} catch (error) {
- console.error("Dashboard data loading error:", error);
- throw error;
+ console.error("Dashboard data fetch error:", error);
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-center py-12">
+ <div className="text-center space-y-2">
+ <p className="text-destructive">데이터를 불러오는데 실패했습니다.</p>
+ <p className="text-muted-foregroucdnd text-sm">{error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다."}</p>
+ </div>
+ </div>
+ </Shell>
+ );
}
}
-// 대시보드 로딩 스켈레톤
function DashboardSkeleton() {
return (
<div className="space-y-6">
@@ -95,35 +98,3 @@ function DashboardSkeleton() {
</div>
);
}
-
-// 에러 표시 컴포넌트
-function DashboardError({ error, reset }: { error: Error; reset: () => void }) {
- return (
- <div className="flex flex-col items-center justify-center py-12 space-y-4">
- <div className="text-center space-y-2">
- <h3 className="text-lg font-semibold">대시보드를 불러올 수 없습니다</h3>
- <p className="text-muted-foreground">
- {error.message || "알 수 없는 오류가 발생했습니다."}
- </p>
- </div>
- <button
- onClick={reset}
- className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
- >
- 다시 시도
- </button>
- </div>
- );
-}
-
-export default async function DashboardPage() {
- return (
- <Shell className="gap-6">
- <ErrorBoundary fallback={DashboardError}>
- <React.Suspense fallback={<DashboardSkeleton />}>
- <DashboardContent />
- </React.Suspense>
- </ErrorBoundary>
- </Shell>
- );
-}
diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/cbe/page.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/[id]/cbe/page.tsx
deleted file mode 100644
index 84379caf..00000000
--- a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/cbe/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsCBECache } from "@/lib/rfqs-tech/validations"
-import { getCBE } from "@/lib/rfqs-tech/service"
-import { CbeTable } from "@/lib/rfqs-tech/cbe-table/cbe-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqCBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsCBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getCBE({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Commercial Bid Evaluation
- </h3>
- <p className="text-sm text-muted-foreground">
- 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br />&quot;발행하기&quot; 버튼을 통해 CBE를 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <CbeTable promises={promises} rfqId={idAsNumber} />
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/layout.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/[id]/layout.tsx
deleted file mode 100644
index 0bb62fe0..00000000
--- a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/layout.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { Metadata } from "next"
-import Link from "next/link"
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { RfqViewWithItems } from "@/db/schema/rfq"
-import { findRfqById } from "@/lib/rfqs-tech/service"
-import { formatDate } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import { ArrowLeft } from "lucide-react"
-
-export const metadata: Metadata = {
- title: "Vendor Detail",
-}
-
-export default async function RfqLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string, id: string }
-}) {
-
- // 1) URL 파라미터에서 id 추출, Number로 변환
- const resolvedParams = await params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
- // 2) DB에서 해당 협력업체 정보 조회
- const rfq: RfqViewWithItems | null = await findRfqById(idAsNumber)
-
- // 3) 사이드바 메뉴
- const sidebarNavItems = [
- {
- title: "Matched Vendors",
- href: `/${lng}/evcp/rfq-tech/${id}`,
- },
- {
- title: "TBE",
- href: `/${lng}/evcp/rfq-tech/${id}/tbe`,
- },
- {
- title: "CBE",
- href: `/${lng}/evcp/rfq-tech/${id}/cbe`,
- },
-
- ]
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- <div className="flex items-center justify-end mb-4">
- <Link href={`/${lng}/evcp/rfq`} passHref>
- <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
- <ArrowLeft className="mr-1 h-4 w-4" />
- <span>RFQ 목록으로 돌아가기</span>
- </Button>
- </Link>
- </div>
- <div className="space-y-0.5">
- {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
- <h2 className="text-2xl font-bold tracking-tight">
- {rfq
- ? `${rfq.projectCode ?? ""} ${rfq.rfqCode ?? ""} 관리`
- : "Loading RFQ..."}
- </h2>
-
- <p className="text-muted-foreground">
- {rfq
- ? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}`
- : ""}
- </p>
- <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate)}</strong>}</h3>
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="lg:w-64 flex-shrink-0">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div>
- </div>
- </div>
- </section>
- </div>
- </>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/page.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/[id]/page.tsx
deleted file mode 100644
index 007270a1..00000000
--- a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getMatchedVendors } from "@/lib/rfqs-tech/service"
-import { searchParamsMatchedVCache } from "@/lib/rfqs-tech/validations"
-import { MatchedVendorsTable } from "@/lib/rfqs-tech/vendor-table/vendors-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsMatchedVCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getMatchedVendors({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Vendors
- </h3>
- <p className="text-sm text-muted-foreground">
- 등록된 협력업체 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>&quot;발행하기&quot; 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <MatchedVendorsTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/tbe/page.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/[id]/tbe/page.tsx
deleted file mode 100644
index 4b226cdc..00000000
--- a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/tbe/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getTBE } from "@/lib/rfqs-tech/service"
-import { searchParamsTBECache } from "@/lib/rfqs-tech/validations"
-import { TbeTable } from "@/lib/rfqs-tech/tbe-table/tbe-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsTBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getTBE({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Technical Bid Evaluation
- </h3>
- <p className="text-sm text-muted-foreground">
- 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>&quot;발행하기&quot; 버튼을 통해 TBE를 전송하면 첨부파일과 함께 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <TbeTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/page.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/page.tsx
deleted file mode 100644
index f35b3632..00000000
--- a/app/[lng]/procurement/(procurement)/rfq-tech/page.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { searchParamsCache } from "@/lib/rfqs-tech/validations"
-import { getRfqs, getRfqStatusCounts } from "@/lib/rfqs-tech/service"
-import { RfqsTable } from "@/lib/rfqs-tech/table/rfqs-table"
-import { getAllOffshoreItems } from "@/lib/items-tech/service"
-
-interface RfqPageProps {
- searchParams: Promise<SearchParams>;
- title: string;
- description: string;
-}
-
-export default async function RfqPage({
- searchParams,
- title = "기술영업 해양 RFQ",
- description = "기술영업 해양 RFQ를 등록하고 관리할 수 있습니다."
-}: RfqPageProps) {
- const search = searchParamsCache.parse(await searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getRfqs({
- ...search,
- filters: validFilters,
- }),
- getRfqStatusCounts(),
- getAllOffshoreItems()
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- {title}
- </h2>
- <p className="text-muted-foreground">
- {description}
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RfqsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/tag-numbering/page.tsx b/app/[lng]/procurement/(procurement)/tag-numbering/page.tsx
deleted file mode 100644
index 44695259..00000000
--- a/app/[lng]/procurement/(procurement)/tag-numbering/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { searchParamsCache } from "@/lib/tag-numbering/validation"
-import { getTagNumbering } from "@/lib/tag-numbering/service"
-import { TagNumberingTable } from "@/lib/tag-numbering/table/tagNumbering-table"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getTagNumbering({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 태그 타입 목록 from S-EDP
- </h2>
- <p className="text-muted-foreground">
- 태그 넘버링을 위한 룰셋을 S-EDP로부터 가져오고 확인할 수 있습니다{" "}
- {/* <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <TagNumberingTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/tasks/page.tsx b/app/[lng]/procurement/(procurement)/tasks/page.tsx
deleted file mode 100644
index 91b946fb..00000000
--- a/app/[lng]/procurement/(procurement)/tasks/page.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { DateRangePicker } from "@/components/date-range-picker"
-import { Shell } from "@/components/shell"
-
-import { FeatureFlagsProvider } from "@/lib/tasks/table/feature-flags-provider"
-import { TasksTable } from "@/lib/tasks/table/tasks-table"
-import {
- getTaskPriorityCounts,
- getTasks,
- getTaskStatusCounts,
-} from "@/lib/tasks/service"
-import { searchParamsCache } from "@/lib/tasks/validations"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getTasks({
- ...search,
- filters: validFilters,
- }),
- getTaskStatusCounts(),
- getTaskPriorityCounts(),
- ])
-
- return (
- <Shell className="gap-2">
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- />
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <TasksTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/tbe-tech/page.tsx b/app/[lng]/procurement/(procurement)/tbe-tech/page.tsx
deleted file mode 100644
index 17b01ce2..00000000
--- a/app/[lng]/procurement/(procurement)/tbe-tech/page.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getAllTBE } from "@/lib/rfqs-tech/service"
-import { searchParamsTBECache } from "@/lib/rfqs-tech/validations"
-import { AllTbeTable } from "@/lib/tbe-tech/table/tbe-table"
-import * as React from "react"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-
-interface IndexPageProps {
- params: {
- lng: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
-
- // URL 쿼리 파라미터에서 타입 추출
- const searchParams = await props.searchParams
-
- // SearchParams 파싱 (Zod)
- const search = searchParamsTBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- // 현재 선택된 타입의 데이터 로드
- const promises = Promise.all([
- getAllTBE({
- ...search,
- filters: validFilters,
- })
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- Technical Bid Evaluation
- </h2>
- <p className="text-muted-foreground">
- 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>
- 체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <AllTbeTable promises={promises}/>
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/tech-project-avl/page.tsx b/app/[lng]/procurement/(procurement)/tech-project-avl/page.tsx
deleted file mode 100644
index d942c5c5..00000000
--- a/app/[lng]/procurement/(procurement)/tech-project-avl/page.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from "react"
-import { redirect } from "next/navigation"
-import { getServerSession } from "next-auth/next"
-import { authOptions } from "@/app/api/auth/[...nextauth]/route"
-import { SearchParams } from "@/types/table"
-import { searchParamsCache } from "@/lib/tech-project-avl/validations"
-import { Skeleton } from "@/components/ui/skeleton"
-import { Shell } from "@/components/shell"
-import { AcceptedQuotationsTable } from "@/lib/tech-project-avl/table/accepted-quotations-table"
-import { getAcceptedTechSalesVendorQuotations } from "@/lib/techsales-rfq/service"
-import { getValidFilters } from "@/lib/data-table"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Ellipsis } from "lucide-react"
-
-export interface PageProps {
- params: Promise<{ lng: string }>
- searchParams: Promise<SearchParams>
-}
-
-export default async function AcceptedQuotationsPage({
- params,
- searchParams,
-}: PageProps) {
- const { lng } = await params
-
- const session = await getServerSession(authOptions)
- if (!session) {
- redirect(`/${lng}/auth/signin`)
- }
-
- const search = await searchParams
- const { page, perPage, sort, filters, search: searchText } = searchParamsCache.parse(search)
- const validFilters = getValidFilters(filters ?? [])
-
- const { data, pageCount } = await getAcceptedTechSalesVendorQuotations({
- page,
- perPage: perPage ?? 10,
- sort,
- search: searchText,
- filters: validFilters,
- })
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 승인된 견적서(해양TOP,HULL)
- </h2>
- <p className="text-muted-foreground">
- 기술영업 승인 견적서에 대한 요약 정보를 확인하고{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 RFQ 코드, 설명, 업체명, 업체 코드 등의 상세 정보를 확인할 수 있습니다.
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* Date range picker can be added here if needed */}
- </React.Suspense>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={12}
- searchableColumnCount={2}
- filterableColumnCount={4}
- cellWidths={["10rem", "15rem", "12rem", "10rem", "10rem", "12rem", "8rem", "12rem", "10rem", "8rem", "10rem", "10rem"]}
- shrinkZero
- />
- }
- >
- <AcceptedQuotationsTable
- data={data}
- pageCount={pageCount}
- />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/tech-vendor-candidates/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendor-candidates/page.tsx
deleted file mode 100644
index 3923863a..00000000
--- a/app/[lng]/procurement/(procurement)/tech-vendor-candidates/page.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { getVendorCandidateCounts, getVendorCandidates } from "@/lib/tech-vendor-candidates/service"
-import { searchParamsTechCandidateCache } from "@/lib/tech-vendor-candidates/validations"
-import { VendorCandidateTable as TechVendorCandidateTable } from "@/lib/tech-vendor-candidates/table/candidates-table"
-import { DateRangePicker } from "@/components/date-range-picker"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsTechCandidateCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getVendorCandidates({
- ...search,
- filters: validFilters,
- }),
- getVendorCandidateCounts()
- ])
-
- return (
- <Shell className="gap-2">
-
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- Vendor Candidates Management
- </h2>
- <p className="text-muted-foreground">
- 수집한 협력업체 후보를 등록하고 초대 메일을 송부할 수 있습니다.
- </p>
- </div>
- </div>
- </div>
-
- {/* 수집일 라벨과 DateRangePicker를 함께 배치 */}
- <div className="flex items-center justify-start gap-2">
- {/* <span className="text-sm font-medium">수집일 기간 설정: </span> */}
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- <DateRangePicker
- triggerSize="sm"
- triggerClassName="w-56 sm:w-60"
- align="end"
- shallow={false}
- showClearButton={true}
- placeholder="수집일 날짜 범위를 고르세요"
- />
- </React.Suspense>
- </div>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <TechVendorCandidateTable promises={promises}/>
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/items/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/items/page.tsx
deleted file mode 100644
index 69c36576..00000000
--- a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/items/page.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-// import { Separator } from "@/components/ui/separator"
-// import { getTechVendorById, getVendorItemsByType } from "@/lib/tech-vendors/service"
-// import { type SearchParams } from "@/types/table"
-// import { TechVendorItemsTable } from "@/lib/tech-vendors/items-table/item-table"
-
-// interface IndexPageProps {
-// // Next.js 13 App Router에서 기본으로 주어지는 객체들
-// params: {
-// lng: string
-// id: string
-// }
-// searchParams: Promise<SearchParams>
-// }
-
-// export default async function TechVendorItemsPage(props: IndexPageProps) {
-// const resolvedParams = await props.params
-// const id = resolvedParams.id
-
-// const idAsNumber = Number(id)
-
-// // 벤더 정보 가져오기 (벤더 타입 필요)
-// const vendorInfo = await getTechVendorById(idAsNumber)
-// const vendorType = vendorInfo.data?.techVendorType || "조선"
-
-// const promises = getVendorItemsByType(idAsNumber, vendorType)
-
-// // 4) 렌더링
-// return (
-// <div className="space-y-6">
-// <div>
-// <h3 className="text-lg font-medium">
-// 공급품목
-// </h3>
-// <p className="text-sm text-muted-foreground">
-// 기술영업 벤더의 공급 가능한 품목을 확인하세요.
-// </p>
-// </div>
-// <Separator />
-// <div>
-// <TechVendorItemsTable
-// promises={promises}
-// vendorId={idAsNumber}
-// vendorType={vendorType}
-// />
-// </div>
-// </div>
-// )
-// } \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/layout.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/layout.tsx
deleted file mode 100644
index 7c389720..00000000
--- a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/layout.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Metadata } from "next"
-
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { findTechVendorById } from "@/lib/tech-vendors/service"
-import { TechVendor } from "@/db/schema/techVendors"
-import { Button } from "@/components/ui/button"
-import { ArrowLeft } from "lucide-react"
-import Link from "next/link"
-export const metadata: Metadata = {
- title: "Tech Vendor Detail",
-}
-
-export default async function SettingsLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string , id: string}
-}) {
-
- // 1) URL 파라미터에서 id 추출, Number로 변환
- const resolvedParams = await params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
- // 2) DB에서 해당 협력업체 정보 조회
- const vendor: TechVendor | null = await findTechVendorById(idAsNumber)
-
- // 3) 사이드바 메뉴
- const sidebarNavItems = [
- {
- title: "연락처",
- href: `/${lng}/evcp/tech-vendors/${id}/info`,
- },
- // {
- // title: "자재 리스트",
- // href: `/${lng}/evcp/tech-vendors/${id}/info/items`,
- // },
- // {
- // title: "견적 히스토리",
- // href: `/${lng}/evcp/tech-vendors/${id}/info/rfq-history`,
- // },
- ]
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- {/* RFQ 목록으로 돌아가는 링크 추가 */}
- <div className="flex items-center justify-end mb-4">
- <Link href={`/${lng}/evcp/tech-vendors`} passHref>
- <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
- <ArrowLeft className="mr-1 h-4 w-4" />
- <span>기술영업 벤더 목록으로 돌아가기</span>
- </Button>
- </Link>
- </div>
- <div className="space-y-0.5">
- {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
- <h2 className="text-2xl font-bold tracking-tight">
- {vendor
- ? `${vendor.vendorCode ?? ""} - ${vendor.vendorName} 상세 정보`
- : "Loading Vendor..."}
- </h2>
- <p className="text-muted-foreground">기술영업 벤더 관련 상세사항을 확인하세요.</p>
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="-mx-4 lg:w-1/5">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="flex-1">{children}</div>
- </div>
- </div>
- </section>
- </div>
- </>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/page.tsx
deleted file mode 100644
index a57d6df7..00000000
--- a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { getTechVendorContacts } from "@/lib/tech-vendors/service"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsContactCache } from "@/lib/tech-vendors/validations"
-import { TechVendorContactsTable } from "@/lib/tech-vendors/contacts-table/contact-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function SettingsAccountPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsContactCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
-
-
- const promises = Promise.all([
- getTechVendorContacts({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Contacts
- </h3>
- <p className="text-sm text-muted-foreground">
- 업무별 담당자 정보를 확인하세요.
- </p>
- </div>
- <Separator />
- <div>
- <TechVendorContactsTable promises={promises} vendorId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/rfq-history/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/rfq-history/page.tsx
deleted file mode 100644
index 4ed2b39f..00000000
--- a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/rfq-history/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-// import { Separator } from "@/components/ui/separator"
-// import { getRfqHistory } from "@/lib/vendors/service"
-// import { type SearchParams } from "@/types/table"
-// import { getValidFilters } from "@/lib/data-table"
-// import { searchParamsRfqHistoryCache } from "@/lib/vendors/validations"
-// import { TechVendorRfqHistoryTable } from "@/lib/tech-vendors/rfq-history-table/rfq-history-table"
-
-// interface IndexPageProps {
-// // Next.js 13 App Router에서 기본으로 주어지는 객체들
-// params: {
-// lng: string
-// id: string
-// }
-// searchParams: Promise<SearchParams>
-// }
-
-// export default async function RfqHistoryPage(props: IndexPageProps) {
-// const resolvedParams = await props.params
-// const lng = resolvedParams.lng
-// const id = resolvedParams.id
-
-// const idAsNumber = Number(id)
-
-// // 2) SearchParams 파싱 (Zod)
-// // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
-// const searchParams = await props.searchParams
-// const search = searchParamsRfqHistoryCache.parse(searchParams)
-// const validFilters = getValidFilters(search.filters)
-
-// const promises = Promise.all([
-// getRfqHistory({
-// ...search,
-// filters: validFilters,
-// },
-// idAsNumber)
-// ])
-
-// // 4) 렌더링
-// return (
-// <div className="space-y-6">
-// <div>
-// <h3 className="text-lg font-medium">
-// RFQ History
-// </h3>
-// <p className="text-sm text-muted-foreground">
-// 협력업체의 RFQ 참여 이력을 확인할 수 있습니다.
-// </p>
-// </div>
-// <Separator />
-// <div>
-// <TechVendorRfqHistoryTable promises={promises} vendorId={idAsNumber} />
-// </div>
-// </div>
-// )
-// } \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/page.tsx
deleted file mode 100644
index 8f542f59..00000000
--- a/app/[lng]/procurement/(procurement)/tech-vendors/page.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { searchParamsCache } from "@/lib/tech-vendors/validations"
-import { getTechVendors, getTechVendorStatusCounts } from "@/lib/tech-vendors/service"
-import { TechVendorsTable } from "@/lib/tech-vendors/table/tech-vendors-table"
-import { TechVendorContainer } from "@/components/tech-vendors/tech-vendor-container"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- // 벤더 타입 정의
- const vendorTypes = [
- { id: "all", name: "전체", value: "" },
- { id: "ship", name: "조선", value: "조선" },
- { id: "top", name: "해양TOP", value: "해양TOP" },
- { id: "hull", name: "해양HULL", value: "해양HULL" },
- ]
-
- const promises = Promise.all([
- getTechVendors({
- ...search,
- filters: validFilters,
- }),
- getTechVendorStatusCounts(),
- ])
-
- return (
- <Shell className="gap-4">
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <TechVendorContainer vendorTypes={vendorTypes}>
- <TechVendorsTable promises={promises} />
- </TechVendorContainer>
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file